package org.zoomdev.zoom.aop.factory.impl;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.zoomdev.zoom.aop.Setter;
import org.zoomdev.zoom.aop.factory.SetterFactory;
import org.zoomdev.zoom.aop.utils.AsmUtils;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.Classes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public final class SetterFactoryImpl extends AsmSingleMethodFactory<Setter, Field> implements SetterFactory {


    public SetterFactoryImpl(ClassLoader classLoader) {
        super(classLoader);
    }

    @Override
    protected String getClassName(Field param) {
        return param.getDeclaringClass().getName() + "." + param.getName() + "$Setter";
    }

    @Override
    protected Setter createImpl(Field field) {
        if (!Modifier.isPublic(field.getModifiers())) {
            Method getter = Classes.getSetter(field.getDeclaringClass(), field.getName(), field.getType());
            if (getter == null) {
                return new FieldSetter(field);
            }
        }
        return super.createImpl(field);
    }

    @Override
    protected byte[] createBytes(String className, Field field) {
        String declaringClassInternalName = Type.getInternalName(field.getDeclaringClass());
        String fieldTypeDescriptor = Type.getDescriptor(field.getType());

        // 创建 ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

        // 定义类
        cw.visit(AsmUtils.VERSION, Opcodes.ACC_PUBLIC, className, null,
                "java/lang/Object", new String[]{Type.getInternalName(Setter.class)});

        // 添加默认构造方法
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();

        // 添加 set 方法
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", null, null);
        mv.visitCode();

        boolean isPublic = Modifier.isPublic(field.getModifiers());
        if (isPublic) {
            // 直接访问 public 字段
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitTypeInsn(Opcodes.CHECKCAST, declaringClassInternalName);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            if (field.getType().isPrimitive()) {
                // 基本类型需要拆箱
                Type primitiveType = Type.getType(field.getType());
                String boxedInternalName = Type.getInternalName(Caster.getWrapClass(field.getType()));
                mv.visitTypeInsn(Opcodes.CHECKCAST, boxedInternalName);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedInternalName,
                        primitiveType.getClassName() + "Value", "()" + fieldTypeDescriptor, false);
            } else {
                mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(field.getType()));
            }
            mv.visitFieldInsn(Opcodes.PUTFIELD, declaringClassInternalName, field.getName(), fieldTypeDescriptor);
        } else {
            // 调用 setter 方法
            Method setter = Classes.getSetter(field.getDeclaringClass(), field.getName(), field.getType());
            if (setter == null) {
                throw new ZoomException("无法为[" + field.getDeclaringClass().getName() + "." + field.getName() + "]创建setter,无对应public字段或者方法");
            }
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitTypeInsn(Opcodes.CHECKCAST, declaringClassInternalName);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            if (field.getType().isPrimitive()) {
                // 基本类型需要拆箱
                Type primitiveType = Type.getType(field.getType());
                String boxedInternalName = Type.getInternalName(Caster.getWrapClass(field.getType()));
                mv.visitTypeInsn(Opcodes.CHECKCAST, boxedInternalName);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedInternalName,
                        primitiveType.getClassName() + "Value", "()" + fieldTypeDescriptor, false);
            } else {
                mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(field.getType()));
            }
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, declaringClassInternalName, setter.getName(),
                    Type.getMethodDescriptor(setter), false);
        }

        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(3, 3);
        mv.visitEnd();

        cw.visitEnd();
        return cw.toByteArray();
    }

    static class FieldSetter implements Setter {

        private final Field field;

        FieldSetter(Field field) {
            this.field = field;
            field.setAccessible(true);
        }

        @Override
        public void set(Object target, Object value) {
            try {
                field.set(target, value);
            } catch (IllegalAccessException e) {
                throw new ZoomException("无法设置值", e);
            }
        }
    }
}
